package com.seer.srd.qingdao

import com.seer.srd.BusinessError
import com.seer.srd.io.modbus.ModbusTcpMasterHelper
import com.seer.srd.scheduler.ThreadFactoryHelper.buildNamedThreadFactory
import io.netty.buffer.ByteBuf
import org.slf4j.LoggerFactory
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.math.ceil

object PlcListener {
    private val logger = LoggerFactory.getLogger(PlcListener::class.java)

    private val plcCfg = CUSTOM_CONFIG.storagePlcConfig

    private val pai = parsePlcAddressInformation()

    private val defaultPlcValues = PlcValues()

    @Volatile
    private var plcValues: PlcValues = defaultPlcValues

    @Volatile
    private var helper: ModbusTcpMasterHelper? = null

    private val executors =
        Executors.newSingleThreadScheduledExecutor(buildNamedThreadFactory("read-plc-value"))

    init {
        // 判断配置文件中PLC的参数是否正常
        plcCfg.check()
        getHelper()
    }

    fun init() {
        executors.scheduleWithFixedDelay(this::readValues, 2000, 500, TimeUnit.MILLISECONDS)
    }

    private fun getHelper(): ModbusTcpMasterHelper {
        if (helper == null) {
            helper = ModbusTcpMasterHelper(plcCfg.host, plcCfg.port)
        }
        return helper!!
    }

    private fun parsePlcAddressInformation(): PlcAddressInfo {
        val addressList = listOf(plcCfg.bigTrayOn, plcCfg.bigEmpty, plcCfg.smallTrayOn, plcCfg.smallEmpty).sorted()
        val first = addressList.first()
        return PlcAddressInfo(
            first,
            addressList.last() - first + 1,
            plcCfg.bigTrayOn - first,
            plcCfg.bigEmpty - first,
            plcCfg.smallTrayOn - first,
            plcCfg.smallEmpty - first
        )
    }

    private fun readValues() {
        try {
            val addressType = plcCfg.addressType
            val res = when (addressType) {
                "0x" -> getHelper().read01Coils(pai.baseAddress, pai.count, plcCfg.unitId, "立库对接模块")
                "1x" -> getHelper().read02DiscreteInputs(pai.baseAddress, pai.count, plcCfg.unitId, "立库对接模块")
                "3x" -> getHelper().read04InputRegisters(pai.baseAddress, pai.count, plcCfg.unitId, "立库对接模块")
                "4x" -> getHelper().read03HoldingRegisters(pai.baseAddress, pai.count, plcCfg.unitId, "立库对接模块")
                else -> throw BusinessError("无法识别 Modbus 地址类型（${addressType}），请检查配置文件！期望值为（0x, 1x, 3x, 4x）")
            } ?: throw BusinessError("读取数据失败：读到的值为 null")

            val newPlcValues =
                if (addressType in listOf("0x", "1x")) parseBoolValues(res) else parseRegisterValues(res)

            updatePlcValues(newPlcValues, "read success")

        } catch (e: Exception) {
            logger.error("read values failed: ", e)
            // 读取数据失败时，需要将所有条件设置为“不可用“的状态！
            updatePlcValues(defaultPlcValues, "read failed")
        }
    }

    /**
     * 解析读取到的 3x 或 4x 类型的地址的值。
     */
    private fun parseRegisterValues(rawData: ByteBuf): PlcValues {
        return PlcValues(
            rawData.getShort(pai.bigTrayOnIndex * 2).toInt(),
            rawData.getShort(pai.bigEmptyIndex * 2).toInt(),
            rawData.getShort(pai.smallTrayOnIndex * 2).toInt(),
            rawData.getShort(pai.smallEmptyIndex * 2).toInt()
        )
    }

    /**
     * 解析读取到的 0x 和 1x 类型的地址的值。
     *
     */
    private fun parseBoolValues(rawData: ByteBuf): PlcValues {
        val i = ceil( pai.count / 8.0).toInt()
        val values: MutableList<Int> = mutableListOf()
        (0 until i).forEach { iv ->
            var value = rawData.getByte(iv).toInt()
            (0 until 8).forEach { _ ->
                values.add(value % 2)
                value /= 2
            }
        }
        return PlcValues(
            values[pai.bigTrayOnIndex],
            values[pai.bigEmptyIndex],
            values[pai.smallTrayOnIndex],
            values[pai.smallEmptyIndex]
        )
    }

    private fun updatePlcValues(newValue: PlcValues, remark: String) {
        if (newValue.toString() != plcValues.toString()) {
            plcValues = newValue
            logger.debug("plc values changed to：$plcValues for $remark")
        }
    }

    fun checkToSiteAvailable(bigTray: Boolean): Boolean {
        return if (bigTray) {
            plcValues.bigTrayOn == 1 && plcValues.bigEmpty == 1
        } else {
            plcValues.smallTrayOn == 1 && plcValues.smallEmpty == 1
        }
    }

}

data class PlcAddressInfo(
    val baseAddress: Int,
    val count: Int,
    val bigTrayOnIndex: Int,
    val bigEmptyIndex: Int,
    val smallTrayOnIndex: Int,
    val smallEmptyIndex: Int
)

data class PlcValues(
    val bigTrayOn: Int = 0,
    val bigEmpty: Int = 0,
    val smallTrayOn: Int = 0,
    val smallEmpty: Int = 0
)
